﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using System.Windows;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using System.Xml.Serialization;
using System.Xml.XPath;
using FFmpegVideoClip.Configuration;
using FFmpegVideoClip.Mode;
using PropertyChanged;

namespace FFmpegVideoClip.Lib.AdvancedFragmentProcessor
{

    [Serializable, AddINotifyPropertyChangedInterface]
    public class AdvancedParametersSchemeModule : IXmlSerializable, IDeepCopy<AdvancedParametersSchemeModule>
    {
        public const string Separator = "#*#";
        public const string ParametersStartFlag = "#*";
        public const string ParametersEndFlag = "*#";
        public static AdvancedParametersSchemeModule[] ComboBoxSelectSourceUIItems { get; private set; } = new AdvancedParametersSchemeModule[] { new() { SchemeName = "剪辑源" } };
        public static AdvancedParametersSchemeModule[] ComboBoxSelectAfterprocessingSourceUIItems { get; private set; } = new AdvancedParametersSchemeModule[] { ComboBoxSelectSourceUIItem, new() { SchemeName = "后处理源" } };
        public static AdvancedParametersSchemeModule ComboBoxSelectSourceUIItem => ComboBoxSelectSourceUIItems[0];
        public static AdvancedParametersSchemeModule ComboBoxSelectAfterprocessingSourceUIItem => ComboBoxSelectSourceUIItems[1];
        /// <summary>
        /// 匹配(#*value*#) 结果带有[#**#]
        /// </summary>
        public static readonly Regex CommandVariableRegex = new Regex(@"#\*(?<name>.*?)\*#", RegexOptions.IgnoreCase);
        /// <summary>
        /// 匹配全部输入项
        /// </summary>
        public static readonly Regex CommandVariableInputMRegex = new Regex(@"#\*(?<name>Input\d?)\*#", RegexOptions.IgnoreCase);
        public static string[] GetCommandVariables(string Command) => CommandVariableRegex.Matches(Command).Select(p => p.Value).ToArray();
        /********************************************************************************************************************************************************************/
        /// <summary>
        /// 处理器(处理程序_如FFmpeg)
        /// </summary>
        public string ProcessorName { get; set; } = string.Empty;

        public Guid SchemeID { get; set; } = Guid.Empty;
        public int UIIndex { get; set; } = -1;
        public bool DurationMode { get; set; } = false;
        public bool EnableSkipOutputSign { get; set; } = false;
        public bool ExistEndTimeArgs { get; private set; } = false;
        public bool ExistCutTimeArgs { get; private set; } = false;
        [DependsOn(nameof(ProcessorName))]
        public bool IsDurationMode
        {
            get
            {
                CallProcessor? cp = Config.Instance.Processors.FirstOrDefault(p => p.ProcessorName.Equals(ProcessorName, StringComparison.OrdinalIgnoreCase));
                if (cp == null)
                    return false;
                return cp.DurationMode && ExistEndTimeArgs && DurationMode;
            }
        }
        /// <summary>
        /// 处理方案名称
        /// </summary>
        public string SchemeName { get; set; } = string.Empty;

        /// <summary>
        /// 命令行_执行参数,[命令参数行中，每个签名的开始使用[#*],结尾使用[*#]]
        /// <para>xxxx#*编码率*#xxxx</para>
        /// </summary>
        public string Command { get; set; } = string.Empty;

        /// <summary>
        /// 命令行中关键参数的签名，用于替换实际的执行参数中的值,[命令参数行中，每个签名的开始使用[#*],结尾使用[*#]]
        /// <para>xxxx#*编码率*#xxxx</para>
        /// </summary>
        public ObservableCollection<ParametersSign> ParametersSignList { get; set; } = new();

        #region 序列化
        public XmlSchema? GetSchema() { return null; }

        public void ReadXml(XmlReader reader)
        {
            reader.MoveToContent();
            bool? ctime = null, etime = null;
            if (!reader.IsEmptyElement)
            {
                string? AttributeValue = reader.GetAttribute(nameof(SchemeID));
                SchemeID = string.IsNullOrWhiteSpace(AttributeValue) ? Guid.Empty : Guid.Parse(AttributeValue);

                SchemeName = reader.GetAttribute(nameof(SchemeName))!;
                ProcessorName = reader.GetAttribute(nameof(ProcessorName))!;

                AttributeValue = reader.GetAttribute(nameof(DurationMode));
                DurationMode = string.IsNullOrWhiteSpace(AttributeValue) ? false : bool.Parse(AttributeValue);

                AttributeValue = reader.GetAttribute(nameof(EnableSkipOutputSign));
                EnableSkipOutputSign = string.IsNullOrWhiteSpace(AttributeValue) ? false : bool.Parse(AttributeValue);

                AttributeValue = reader.GetAttribute(nameof(ExistEndTimeArgs));
                etime = string.IsNullOrWhiteSpace(AttributeValue) ? false : bool.Parse(AttributeValue);

                AttributeValue = reader.GetAttribute(nameof(ExistCutTimeArgs));
                ctime = string.IsNullOrWhiteSpace(AttributeValue) ? null : bool.Parse(AttributeValue);

                reader.ReadStartElement();

                reader.ReadStartElement(nameof(Command));
                Command = reader.ReadContentAsString();
                reader.ReadEndElement();

                reader.ReadStartElement(nameof(ParametersSign));
                string psString = reader.ReadContentAsString();
                if (!string.IsNullOrEmpty(psString))
                {
                    string[] psa = psString.Split(Separator);
                    if (psa.Length > 0)
                    {
                        for (int i = 0; i < psa.Length; i = i + 3)
                            ParametersSignList.Add(new ParametersSign { ShowName = psa[i], SignName = psa[i + 1], Parameter = psa[i + 2] });
                    }
                }
                reader.ReadEndElement();

                reader.ReadEndElement();
            }
            if (ctime == null || etime == null)
                GetExistTimeFlag();
            else { ExistEndTimeArgs = etime.Value; ExistCutTimeArgs = ctime.Value; }
        }

        public void WriteXml(XmlWriter writer)
        {
            writer.WriteAttributeString(nameof(SchemeID), SchemeID.ToString());
            writer.WriteAttributeString(nameof(SchemeName), SchemeName);
            writer.WriteAttributeString(nameof(ProcessorName), ProcessorName);
            writer.WriteAttributeString(nameof(DurationMode), DurationMode.ToString());
            writer.WriteAttributeString(nameof(EnableSkipOutputSign), EnableSkipOutputSign.ToString());
            writer.WriteAttributeString(nameof(ExistEndTimeArgs), ExistEndTimeArgs.ToString());
            writer.WriteAttributeString(nameof(ExistCutTimeArgs), ExistCutTimeArgs.ToString());

            writer.WriteStartElement(nameof(Command));
            writer.WriteCData(Command);
            writer.WriteEndElement();

            writer.WriteStartElement(nameof(ParametersSign));
            StringBuilder sb = new StringBuilder();
            if (ParametersSignList.Count > 0)
            {
                sb.Append($"{ParametersSignList[0].ShowName}{Separator}{ParametersSignList[0].SignName}{Separator}{ParametersSignList[0].Parameter}");
                if (ParametersSignList.Count > 1)
                {
                    foreach (ParametersSign param in ParametersSignList.Skip(1))
                    {
                        sb.Append($"{Separator}{param.ShowName}{Separator}{param.SignName}{Separator}{param.Parameter}");
                    }
                }
            }
            writer.WriteCData(sb.ToString());
            writer.WriteEndElement();
        }
        #endregion

        public AdvancedParametersSchemeModule DeepCopy()
        {
            AdvancedParametersSchemeModule module = new();
            DeepCopyTo(module);
            return module;
        }
        public void DeepCopyModeSet(AdvancedParametersSchemeModule module)
        {
            SchemeID = module.SchemeID;
            SchemeName = module.SchemeName;
            ProcessorName = module.ProcessorName;
            Command = module.Command;
            DurationMode = module.DurationMode;
            EnableSkipOutputSign = module.EnableSkipOutputSign;
            ExistCutTimeArgs = module.ExistCutTimeArgs;
            ExistEndTimeArgs = module.ExistEndTimeArgs;
            ParametersSignList.Clear();
            foreach (ParametersSign param in module.ParametersSignList)
                ParametersSignList.Add(new() { SignName = param.SignName, ShowName = param.ShowName, Parameter = param.Parameter });
        }
        public void DeepCopyTo(AdvancedParametersSchemeModule module)
        {
            module.SchemeID = SchemeID;
            module.SchemeName = SchemeName;
            module.ProcessorName = ProcessorName;
            module.Command = Command;
            module.DurationMode = DurationMode;
            module.EnableSkipOutputSign = EnableSkipOutputSign;
            module.ExistCutTimeArgs = ExistCutTimeArgs;
            module.ExistEndTimeArgs = ExistEndTimeArgs;
            module.ParametersSignList.Clear();
            foreach (ParametersSign param in ParametersSignList)
                module.ParametersSignList.Add(new() { SignName = param.SignName, ShowName = param.ShowName, Parameter = param.Parameter });
        }
        public void CopyTo(AdvancedParametersSchemeModule target, bool EnableOverwriteParameterValue = true)
        {
            target.ProcessorName = ProcessorName;
            target.SchemeName = SchemeName;
            target.Command = Command;
            target.DurationMode = DurationMode;
            target.EnableSkipOutputSign = EnableSkipOutputSign;
            target.ExistCutTimeArgs = ExistCutTimeArgs;
            target.ExistEndTimeArgs = ExistEndTimeArgs;

            ObservableCollection<ParametersSign> oldList = target.ParametersSignList;
            target.ParametersSignList = CloneParametersSignList();

            if (!EnableOverwriteParameterValue)
            {
                ParametersSign? psTemp = null;
                target.ParametersSignList.ForEach(p =>
                {
                    psTemp = oldList.FirstOrDefault(o => o.SignName.Equals(p.SignName));
                    if (psTemp is null)
                        return;
                    p.Parameter = psTemp.Parameter;
                });
            }
        }
        public void GenerateArgumentsList()
        {
            string[] pNames = GetCommandVariables(Command).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();

            if (!pNames.Any(p => p.Equals(ParametersSign.InputSignName) || p.Equals(ParametersSign.InputSignNames_FFmpeg)))
            {
                MessageBoxEx.Show(Application.Current.MainWindow, "输入签名缺失", "签名规则错误", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }
            if (!pNames.Any(p => p.Equals(ParametersSign.OutputSignName)))
            {
                MessageBoxEx.Show(Application.Current.MainWindow, "输出签名缺失", "签名规则错误", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }
            Dictionary<string, ParametersSign> dic = ParametersSignList.Distinct(p => p.SignName).ToDictionary(k => k.SignName, v => v);

            ParametersSignList.Clear();
            if (dic.Count > 0)
            {
                foreach (string item in pNames)
                {
                    if (dic.ContainsKey(item))
                        ParametersSignList.Add(dic[item]);
                    else
                        ParametersSignList.Add(new ParametersSign { SignName = item });
                }
            }
            else
            {
                foreach (string item in pNames)
                    ParametersSignList.Add(new ParametersSign { SignName = item });
            }
            GetExistTimeFlag();
        }
        private void GetExistTimeFlag() { ExistEndTimeArgs = ParametersSignList.Any(p => p.SignName.Equals(ParametersSign.ETime)); ExistCutTimeArgs = ExistEndTimeArgs ? true : ParametersSignList.Any(p => p.SignName.Equals(ParametersSign.STime)); }
        /********************************************************************************************************************************************************************/

        /// <summary>
        /// 获取处理器实例
        /// </summary>
        /// <returns></returns>
        public CallProcessor GetCallProcessor()
        {
            try
            {
                return Config.Instance.Processors.First(p => p.ProcessorName.Equals(ProcessorName, StringComparison.OrdinalIgnoreCase));
            }
            catch (Exception ex)
            {
                StringBuilder sb = new StringBuilder();
                Config.Instance.Processors.ForEach(p => sb.Append($"{p.ProcessorName},"));
                throw new InvalidOperationException($"获取处理器失败:未在表单中找到目标[{ProcessorName}],当前列表:{{{sb.ToString().TrimEnd(',')}}}", ex);
            }
        }
        public AdvancedParametersSchemeModule SetName(string SchemeName)
        {
            this.SchemeName = SchemeName;
            return this;
        }

        public string GenerateCommandArgument()
        {
            StringBuilder sb = new StringBuilder(Command);

            //读取信息文件后，
            if (GetInputsOFInfoParametersISInPath(out ParametersSign[] ParametersSigns))
            {
                ConfigurationInformationSettings(this,ParametersSigns);
                
                //该输入参数对命令行来说是无效项，需要从命令行中删除掉
                foreach (ParametersSign param in ParametersSigns)
                    sb.Replace($"{param.SignName}", "");
            }

            //输出目标是信息文件的输出签名项，且信息文件的写入不是被调用的程序输出，而是该程序从返回流中读取后写入，该条件下，该输出参数对命令行来说是无效项，需要从命令行中删除掉，该项从UI上指定设置
            if (EnableSkipOutputSign)
                sb.Replace($"\"{ParametersSign.OutputSignName}\"", "");
            
           
            foreach (ParametersSign item in ParametersSignList)
                sb.Replace(item.SignName, item.Parameter.Trim().Equals(ParametersSign.EmptyParameter, StringComparison.OrdinalIgnoreCase) ? string.Empty : item.Parameter.Trim());
            return sb.ToString();
        }

        public bool GetParametersSign(string Sign, out ParametersSign? Result)
        {
            Result = ParametersSignList.FirstOrDefault(p => p.SignName.Equals(Sign, StringComparison.OrdinalIgnoreCase));
            if (Result == null)
                return false;
            return true;
        }
        
        public bool GetFFmpegMultiInput(out ParametersSign? Inputs) => GetParametersSign(ParametersSign.InputSignNames_FFmpeg, out Inputs);
        
        public bool GetInputs(out ParametersSign[] Inputs)
        {
            Inputs = ParametersSignList.Where(p => CommandVariableInputMRegex.IsMatch(p.SignName)).ToArray();
            if (Inputs is not null && Inputs.Length > 0)
                return true;
            return false;
        }
        
        //获取信息值签名的参数项目
        public bool GetInputInfoParameters(out ParametersSign[] Inputs)
        {
            Inputs = ParametersSignList.Where(p => p.SignName.StartsWith(ParametersSign.InputInfoPattern, StringComparison.OrdinalIgnoreCase)).ToArray();
            if (Inputs is not null && Inputs.Length > 0)
                return true;
            return false;
        }
        
        //获取-读取信息文件的输入项-该项只有在完成输入路径的再赋值的配置后才有效~！推荐在GenerateCommandArgument的前一步才使用
        public bool GetInputsOFInfoParametersISInPath(out ParametersSign[] Inputs)
        {
            Inputs = null!;
            if (GetInputs(out ParametersSign[] _Inputs))
            {
                Inputs = _Inputs.Where(p =>
                {
                    string _Extension = Path.GetExtension(p.Parameter);
                    if (_Extension.Equals(ParametersSign.LogInfoExtension, StringComparison.OrdinalIgnoreCase)
                    || _Extension.Equals(".json", StringComparison.OrdinalIgnoreCase)
                    || _Extension.Equals(".xml", StringComparison.OrdinalIgnoreCase))
                        return true;
                    return false;
                }).ToArray();
            }
            if (Inputs.Any())
                return true;
            return false;
        }
        
        public ParametersSign GetInput()
        {
            ParametersSign? Result;
            if (!GetParametersSign(ParametersSign.InputSignName, out Result) && !GetParametersSign(ParametersSign.InputSignNames_FFmpeg, out Result))
                throw new ArgumentException($"方案[{SchemeName}] 缺少输入参数");
            return Result!;
        }
        
        public ParametersSign GetOutput()
        {
            if (!GetParametersSign(ParametersSign.OutputSignName, out ParametersSign? Result))
                throw new ArgumentException($"方案[{SchemeName}] 缺少输出参数");
            return Result!;
        }

        public ObservableCollection<ParametersSign> CloneParametersSignList()
        {
            ObservableCollection<ParametersSign> result = new();
            foreach (ParametersSign ps in ParametersSignList)
                result.Add(ps.Clone());
            return result;
        }


        public static readonly HashSet<string> ExclusionsItemsKeys = new HashSet<string>() { ParametersSign.InputSignName, ParametersSign.OutputSignName, ParametersSign.STime, ParametersSign.ETime };
        public bool SchemeModuleEquals(AdvancedParametersSchemeModule? obj) => obj is not null && ParametersSignList.Count == obj.ParametersSignList.Count && StringComparer.OrdinalIgnoreCase.Equals(Command, obj.Command)
            && ParametersSignList.Where(p => !ExclusionsItemsKeys.Contains(p.SignName))
                .Concat(obj.ParametersSignList.Where(p => !ExclusionsItemsKeys.Contains(p.SignName)))
                .GroupBy(p => p.SignName, StringComparer.OrdinalIgnoreCase)
                .All(p => p.Count() == 2 && StringComparer.OrdinalIgnoreCase.Equals(p.First(), p.Last()));

        public void UpdateIndexValues() => ParametersSignList.ForEach(p => p.UpdateIndexValue());
        public static void ConfigurationInformationSettings(AdvancedParametersSchemeModule module, params ParametersSign[] Inputs)
        {
            if (!module.GetInputInfoParameters(out ParametersSign[] InputParameters))
                return;
            InputParameters.GroupBy(p => p.Parameter, StringComparer.Ordinal).ForEach(p => 
            {
                ParametersSign[] items = p.ToArray();
                int index = items.First().ParameterIndex;
                string _InfofilePath = Inputs.First(p => p.ParameterIndex == index).Parameter;
                string _Extensions = Path.GetExtension(_InfofilePath);
                if (_Extensions.Equals(".json", StringComparison.OrdinalIgnoreCase))
                    ParameterConfigurationFile_json(module, _InfofilePath, items);
                else if (_Extensions.Equals(".xml", StringComparison.OrdinalIgnoreCase))
                    ParameterConfigurationFile_xml(_InfofilePath, items);
                else
                    ParameterConfigurationFile_txt(_InfofilePath, items);
            });

            IEnumerable<ParametersSign> miss = InputParameters.Where(p => !p.HasParameter);
            if(miss.Any())
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine("配置额外信息参数异常：");
                sb.AppendLine("{");
                sb.AppendLine("配置失败的参数项：");
                sb.AppendLine("【");
                foreach (ParametersSign sign in miss) 
                {
                    sb.AppendLine($"{{[{sign.ShowName}][{sign.SignName}]}}");
                }
                sb.AppendLine("】");
                sb.AppendLine("文件信息：");
                foreach (ParametersSign Input in Inputs)
                {
                    sb.AppendLine($"{Input.Parameter}");
                    sb.AppendLine("【");
                    sb.AppendLine(File.ReadAllText(Input.Parameter));
                    sb.AppendLine("】");
                }
                sb.AppendLine("]");
                throw new AdvancedParametersSchemeModuleException(sb.ToString());
            }
        }
        public static void ParameterConfigurationFile_txt(string FilePath, params ParametersSign[] signs)
        {
            string txt = File.ReadAllText(FilePath, Encoding.UTF8);
            if (Tools.GetJsonFromLog(txt, out JsonNode[] jsonNodes))
                ParameterConfigurationFile_json(jsonNodes, signs);
            ParameterConfigurationFile_txt(signs.Where(p => !p.HasParameter), txt);
        }

        private static void ParameterConfigurationFile_txt(IEnumerable<ParametersSign> signs, string txt)
        {
            string MatcheItem;
            string? result;
            foreach (ParametersSign item in signs)
            {
                MatcheItem = item.SignName.Remove(0, ParametersSign.InputInfoPattern.Length).TrimEnd('*', '#');
                result = InputParameterItemSelectTools.TextSelect(MatcheItem, Regex.Matches(txt, $"(?<={MatcheItem}[\\s:=;'\"-]+)\\S+"));
                if (string.IsNullOrWhiteSpace(result))
                    continue;
                item.Parameter = result;
                CheckAndSetHasParameter(item);
            }
        }

        public static void ParameterConfigurationFile_xml(string FilePath, params ParametersSign[] signs)
        {
            XElement root = XElement.Load(FilePath);
            string sign;
            foreach (ParametersSign item in signs)
            {
                sign = item.SignName.Remove(0, ParametersSign.InputInfoPattern.Length).TrimEnd('*', '#');
                item.Parameter = GetXmlValue(root.XPathEvaluate(sign))!;
                CheckAndSetHasParameter(item);
            }
        }
        private static string? GetXmlValue(object obj)
        {
            try
            {
                System.Collections.IEnumerator ir = ((System.Collections.IEnumerable)obj).GetEnumerator();
                ir.MoveNext();
                if (ir.Current is XElement xe)
                    return xe.Value;
                else if (ir.Current is XAttribute xa)
                    return xa.Value;
                return null!;
            }
            catch (Exception ex)
            {
                LogManage.WriteExceptionInfo("xml类型转换失败:", ex);
                return null!;
            }
        }
        public static void ParameterConfigurationFile_json(AdvancedParametersSchemeModule module, string FilePath, params ParametersSign[] signs)
        {
            string txt = File.ReadAllText(FilePath, Encoding.UTF8);

            JsonNode? json = JsonNode.Parse(txt);
            if (json is null)
                throw new ArgumentException($"未成功解析json：模块:[{module.SchemeName}],文件路径:[{FilePath}]\r\n文件:\r\n{{\r\n{txt}\r\n}}");
            ParameterConfigurationFile_json([json], signs);    
        }

        public static void ParameterConfigurationFile_json(JsonNode[] jsonNodes, params ParametersSign[] signs)
        {
            string sign;
            foreach (ParametersSign item in signs)
            {
                foreach (JsonNode json in jsonNodes)
                {
                    sign = item.SignName.Remove(0, ParametersSign.InputInfoPattern.Length).TrimEnd('*', '#');
                    if (!TryPathQuery(json, sign, out JsonNode? node))
                        continue;
                    item.Parameter = node?.GetValue<object>()?.ToString() ?? string.Empty;
                    CheckAndSetHasParameter(item);
                    break;
                }
            }
        }

        private static bool TryPathQuery(JsonNode root, string path, out JsonNode? node)
        {
            node = root;
            foreach (string key in path.Split('\\', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
            {
                node = node?[key];
                if (node == null)
                    return false;
            }
            return true;
        }
        private static void CheckAndSetHasParameter(ParametersSign sign) { if (!string.IsNullOrWhiteSpace(sign.Parameter)) sign.HasParameter = true; }

        [System.Serializable]
        public class AdvancedParametersSchemeModuleException : System.Exception
        {
            public AdvancedParametersSchemeModuleException() { }
            public AdvancedParametersSchemeModuleException(string message) : base(message) { }
            public AdvancedParametersSchemeModuleException(string message, System.Exception inner) : base(message, inner) { }
        }
    }



}
